<template>
	<view class="leo-drag" id="leo-drag" :style="[{'touch-action':isStopPropagation?'none':'auto'}]">
		<!-- touch-action: 阻止滚动 -->
		<movable-area :style="[getAreaStyle]" v-if="showArea">
			<movable-view v-if="isStopPropagation" v-for="(item, index) in showList" :animation="animation" :direction="direction" :key="item.key"
				:damping="damping" :x="item.x" :y="item.y" :disabled="longpress ? disabled : false"
				@longpress="controlLongpress(index)" @touchstart.stop="handleDragStart(index)" @change="handleMoving"
				@touchend="handleDragEnd" :style="[getViewStyle]" class="base-drag-wrapper">
				<!-- 外部使用模板接收数据 -->
				<!-- <leo-drag :column="3" ref="myDragAndDrop" @getList="getDropList" v-model:list="dragAndDrop">
					<template #content={data}>
						<view class="drop" :style="[{'transform':data.index === data.activeIndex?'scale(1.05)':'scale(1)',}]">
							<view class="flex flex-center" style="background-color: aqua;">
								<view class="ellipsis">
									{{data.name}}123456789123456789123456789
								</view>
								<uni-icons class="flex-none" @touchstart="changeDisabled(data.index)" style="margin-left: auto;" type="bars" size="30"></uni-icons>
							</view>
						</view>
					</template>
				</leo-drag> -->
				<view class="slotContent" ref="slotContent" :style="[slotContentStyle]" >
					<slot :data="{...item,index,activeIndex}" name="content"></slot>
				</view>
			</movable-view>
			<movable-view v-else v-for="(item, index) in showList" :animation="animation" :direction="direction" :key="item.key"
				:damping="damping" :x="item.x" :y="item.y" :disabled="longpress ? disabled : false"
				@longpress="controlLongpress(index)" @touchstart="handleDragStart(index)" @change="handleMoving"
				@touchend="handleDragEnd" :style="[getViewStyle]" class="base-drag-wrapper">
				<view class="slotContent" ref="slotContent" :style="[slotContentStyle]" >
					<slot :data="{...item,index,activeIndex}" name="content"></slot>
				</view>
			</movable-view>
		</movable-area>
		<!-- 可用于遮罩指引提示，考虑定位的zindex问题，所以使用插槽插进来 -->
		<slot name="custom"></slot>
	</view>
</template>
<script>
	export default {
		props: {
			slotContentStyle:{
				type:Object,
				default: () => {}
			},
			list: {		// 列表
				type: Array,
				default: () => []
			},
			column: {	// 每行显示多少数量
				type: Number,
				default: 1
			},
			height: {	// 可拖拽的区域宽度，auto 为根据子级高度(itemHeight)计算
				type: String,
				default: 'auto'
			},
			itemHeight: {// 每个子元素的高度，为auto时，计算最高元素
				type: String,
				default: 'auto'
			},
			direction: {	// 拖动方向
				type: String,
				default: 'all',
				validator: value => {
					return ['all', 'vertical', 'horizontal', 'none'].includes(value);
				}
			},
			animation: { 	// 拖动时开启动画
				type: Boolean,
				default: true
			},
			damping: {		// 用于控制x或y改变时的动画和过界回弹的动画
				type: Number,
				default: 20
			},
			swapMode:{			// 替换模式：true:位置后排，false：两位替换
				type: Boolean,
				default: true
			},
			longpress: {	// 是否禁用
				type: Boolean,
				default: true
			},
			supportLongpress: {	// 是否支持长按
				type: Boolean,
				default: false
			},
			isStopPropagation: {	// 是否阻止事件冒泡: 可用于判断是否拖拽状态
				type: Boolean,
				default: false
			}
		},
		data() {
			return {
				showList: [],		// 显示的数据
				disabled: true,		// 是否允许拖动
				activeIndex: -1,	// 当前拖动的item
				moveToIndex: -1,	// 当前拖动的item > 拖动到那一块： 也就是下一块item
				oldIndex: -1,		// 上一块item的位置
				tempDragInfo: {		// 计算拖动的值
					x: '',
					y: ''
				},
				cloneList: [],			// 克隆的list，也就是改了后的showList数据，需要使用cloneList
				itemMaxHeight: "80px",
				width:'100%',
				showArea: false,		// 在nvue，会出现空白或者部分元素不显示的问题
			}
		},
		created() {
			// this.initList(this.showList, true);  // 新数据和是否需要重新设置高度,数据更新请在外部调用：this.$refs.leoDrag.initList(newList, true);
			// 拖动请在外部使用@touchstart 事件 调用：this.$refs.leoDrag.handleLongpress(index);
			// this.showList = this.deepCopy(this.list) || [];
		},
		mounted() {
			// this.setLeoDrag();
		},
		watch:{ 
			
			"getViewStyle":{
				// 获取item宽高
				handler(newValue){
					this.$emit("getViewStyle",newValue)
				},
				deep:true
			},
			"getAreaStyle":{
				// 获取可拖拽区域宽高
				handler(newValue){
					this.$emit("getAreaStyle",newValue)
				},
				deep:true
			},
			"getItemHeight":{
				// 获取item高度
				handler(newValue){
					this.$emit("getItemHeight",newValue)
				},
				deep:true
			},
		},
		computed: {
			getAreaStyle() {
				// 给可拖动区域设置宽高
				const width = this.getRealWidth(this.width);
				let style = {
					width: width + 'px',
					height: this.height !== 'auto' ? this.height : Math.ceil(this.showList.length / this.column) * this.getItemHeight + 'px'
				};
				return style;
			},
			getViewStyle() {
				// 给item设置高度和宽度
				let {getItemHeight,getItemWidth} = this;
				return {
					width: getItemWidth + 'px',
					height: getItemHeight + 'px',
				};
			},
			getItemHeight() {
				// 获取item的高度
				// parseFloat的用法：
				// parseFloat("3.14");          // 3.14
				// parseFloat("  42.7  ");      // 42.7，前后的空格会被忽略
				// parseFloat("3.14abc");        // 3.14，遇到字母停止解析
				// parseFloat("abc3.14");        // NaN，开头是字母，无法解析
				// parseFloat("100");            // 100，整数也被成功解析为浮点数
				// parseFloat("0.5");            // 0.5
				// parseFloat("1e3");            // 1000，科学计数法
				let value = this.itemHeight === 'auto'?parseFloat(this.itemMaxHeight):parseFloat(this.itemHeight);
				return value;
			},
			getItemWidth() {
				// 获取item的宽度
				if (this.column === 0) return;
				const width = this.getRealWidth(this.width);
				return (parseFloat(width) / this.column).toFixed(2);
			},
		},
		methods: {
			init(list){
				this.showList = this.deepCopy(list) || [];
				this.setLeoDrag();
			},
			setLeoDrag(size = 5) {
				// 因为可能获取的宽度是0，需要需要重调
				if (size < 0) return;
				this.$nextTick(() => {
					const query = uni.createSelectorQuery().in(this);
					query.select("#leo-drag").boundingClientRect((data) => {
						if (data.width < 50) {
							this.$nextTick(()=>{
								size = size - 1;
								this.setLeoDrag(size);
							})
							return
						}
						this.width = data.width;
						this.$nextTick(()=>{
							this.initList(this.showList, true); // 初始化 dom
						})
					}).exec();
				})
			},
			controlLongpress(i){
				if(this.supportLongpress){
					this.handleLongpress(i);
				}
			},
			//长按事件或者在外部直接调用，才允许拖动
			handleLongpress(index) {
				this.disabled = false;
				// 哪个开始活动
				this.activeIndex = index;
				// 记录一下旧的下标
				this.oldIndex = index;
			},
			// 拖拽--开始-摁下触发
			handleDragStart(index) {
				// // 哪个开始活动
				// this.activeIndex = index;
				// // 记录一下旧的下标
				// this.oldIndex = index;
			},
			//拖拽中
			handleMoving(e) {
				if (e.detail.source !== 'touch') return;
				const {x,y} = e.detail;
				Object.assign(this.tempDragInfo, {x,y}); // 改变dom位置，拖拽的位置
				
				// x 手指按下拖动，产生的位置，超出了item的宽度，那么就改变下标，包括y轴。
				const currentX = Math.floor((x + this.getItemWidth / 2) / this.getItemWidth); 
				const currentY = Math.floor((y + this.getItemHeight / 2) / this.getItemHeight); 
				
				// moveToIndex：通过计算横排数量，偏移量（ x，y ），得出下标位置
				this.moveToIndex = Math.min(currentY * this.column + currentX, this.showList.length - 1);
				if (this.oldIndex !== this.moveToIndex && this.oldIndex !== -1 && this.moveToIndex !== -1) {
					if(this.swapMode){
						// 排序替换
						const newList = this.deepCopy(this.cloneList);
						let splicItem = newList.splice(this.activeIndex, 1)[0]
						newList.splice(this.moveToIndex, 0,splicItem);
						// 判断如何替换位置
						this.showList.forEach((item, index) => {
							if (index !== this.activeIndex) {
								const itemIndex = newList.findIndex(val => val.dropId === item.dropId);
								[item.x, item.y] = this.getPosition(itemIndex);
							}
						});
					} else{
						// 两点换位
						const oldList = this.deepCopy(this.cloneList);
						this.showList.forEach((item, index) => {
							// 说明需要把不需要换位的下标替回来
							if(index === this.oldIndex){
								[item.x, item.y] = this.getPosition(this.oldIndex);
							}
							// 当前移动的下标和目标下标互换位置
							if(index === this.moveToIndex){
								[item.x, item.y] = this.getPosition(this.activeIndex);
							}
						});
					}
					this.oldIndex = this.moveToIndex;
				}
			},
			//拖拽结束
			handleDragEnd(e) {
				setTimeout(()=>{
					if (this.disabled) return;  // 为点击不能出发
					if (this.moveToIndex !== -1 && this.activeIndex !== -1 && this.moveToIndex !== this.activeIndex) {
						// 拖动了的话，并且目标位置和活动位置不一样，才来这里
						if(this.swapMode){
							this.cloneList.splice(this.moveToIndex, 0, ...this.cloneList.splice(this.activeIndex, 1));
						} else{
							let active = this.cloneList[this.activeIndex];	// 当前下标
							let move = this.cloneList[this.moveToIndex];	// 目标下标
							// 替换
							this.cloneList[this.activeIndex] = move;
							this.cloneList[this.moveToIndex] = active;
						}
					}
					this.initList(this.cloneList);
					const endList = this.showList.map(item => this.omit(item, ['x', 'y', 'key','dropId']));
					
					this.$emit("update:list",endList);	// vue3 的更新方法
					this.$emit("getList",endList);	// 更新列表
					 
					this.activeIndex = -1;
					this.oldIndex = -1;
					this.moveToIndex = -1;
					this.disabled = true;
				})
				this.$emit('handleDragEnd')
			},
			//获取当前的位置
			getPosition(index) {
				// 通过计算重新算换 偏移单位。
				const x = (index % this.column) * this.getItemWidth;
				const y = Math.floor(index / this.column) * this.getItemHeight;
				return [x, y];
			},
			// 省略初始化时添加的 x，y和key
			omit(obj, args) {
				if (!args) return obj;
				const newObj = {};
				const isString = typeof args === 'string';
				const keys = Object.keys(obj).filter(item => {
					if (isString) {
						return item !== args;
					}
					return !args.includes(item);
				});
 
				keys.forEach(key => {
					if (obj[key] !== undefined) newObj[key] = obj[key];
				});
				return newObj;
			},
			// 数据的长度不变的情况下-更新数据-减少加载带来的画面卡顿
			updataList(list){
				// 注意做好拖拽时规避 更新
				if(this.isStopPropagation) return;  // 拖拽中状态不可更新数据
				const newList = this.deepCopy(list);
				for (let i = 0; i < newList.length; i++) {
					this.showList[i] = {...this.showList[i],...newList[i]}
				}
				this.cloneList = this.deepCopy(this.showList);
			},
			// 初始化
			initList(list = [],updata = false) {
				const newList = this.deepCopy(list);
				// 给每个item添加一x，y和key
				this.showList = newList.map((item, index) => {
					const [x, y] = this.getPosition(index);
					let data = {
						...item,
						x,
						y,
						dropId:index+1
					}
					let key = "slot"+Math.random() + index;
					if(this.activeIndex > -1 && this.moveToIndex === -1){
						// 原地点击了一下
						key = item.key;
					} else if(this.activeIndex > -1 && this.moveToIndex > -1){
						// 换位了
						if(this.moveToIndex > this.activeIndex){
							// 被换位的下标 > 移动的下标：说明是往前拖
							if(index < this.activeIndex || index > this.moveToIndex){
								key = item.key;
							}
						} else{
							if(index > this.activeIndex || index < this.moveToIndex){
								key = item.key;
							}
						}
					}
					// 判断拖动位置的元素是那个
					data.key = key;
					return data;
				});
				this.cloneList = this.deepCopy(this.showList);
				this.$nextTick(()=>{
					this.showArea = true;
				})
				if (updata && this.itemHeight === "auto") {
					// 获取到最高的item
					this.$nextTick(() => {
						setTimeout(async () => {
							// #ifdef APP-NVUE
							this.showArea = false;
							const dom = uni.requireNativePlugin('dom') // 引入原生插件
							let max = 0;
							for (let i = 0; i < this.$refs['slotContent'].length; i++) {
								await new Promise((resolve, reject) => {
									dom.getComponentRect(this.$refs['slotContent'][i], res => {
										let size = res.size;
										if (size.height > max) {
											max = size.height;
										}
										resolve();
									})
								})
							}
							this.itemMaxHeight = max + "px";
							this.$nextTick(() => {
								this.initList(this.cloneList)
							})
							// #endif
							// #ifndef APP-NVUE
							const query = uni.createSelectorQuery().in(this);
							query.selectAll(".slotContent").boundingClientRect((data) => {
								let domList = JSON.parse(JSON.stringify(data))
								let max = 0;
								for (let i = 0; i < domList.length; i++) {
									if (domList[i].height > max) {
										max = domList[i].height;
									}
								}
								this.itemMaxHeight = max + "px";
								this.initList(this.cloneList)
							}).exec();
							// #endif
						}, 0)
					})
				}
			},
			// 克隆数据
			deepCopy(source) {
				return JSON.parse(JSON.stringify(source));
			},
			//获取实际的宽度
			getRealWidth(w) {
				let width = w + "";
				if (width.includes('%')) {
					const windowWidth = uni.getSystemInfoSync().windowWidth;
					width = windowWidth * (parseFloat(width) / 100);
				}
				return width;
			},
		}
	}
</script>
 
<style lang="scss" scoped>
	.base-drag-wrapper {
		opacity: 1;
		z-index: 1;
		color: #212121;
		display: flex;
		align-items: center;
		flex-wrap: wrap;
		/* #ifndef APP-NVUE */
		width: 100%;
		/* #endif */
	}
	.slotContent{
		/* #ifndef APP-NVUE */
		width: 100%;
		/* #endif */
	}
	.leo-drag{
		/* #ifndef APP-NVUE */
		width: 100%;
		/* #endif */
	}
	.loading {
		color: pink;
	}
</style>